using System;
using Microsoft.SPOT;
using System.Threading;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;

#if MINI
using SecretLabs.NETMF.Hardware.NetduinoMini;
#else
using SecretLabs.NETMF.Hardware.Netduino;
#endif

namespace codebase
{
    public class NDSTouchscreen
    {
        public event TouchDownHandler TouchDownEvent;
        public event TouchUpHandler TouchUpEvent;
        public event TouchMoveHandler TouchMoveEvent;

        // Touchscreen(NDS)-> Netduino
        // Pin 1 (top)    -> A1 + 10k Pullup,
        // Pin 2 (right)  -> A0 + 10k Pullup,
        // Pin 3 (bottom) -> A3 + 10k Pullup,
        // Pin 4 (left)   -> A2 + 10k Pullup
        private static Cpu.Pin XLow;
        private static Cpu.Pin YLow;
        private static Cpu.Pin XHigh;
        private static Cpu.Pin YHigh;
        private static Cpu.AnalogChannel _XLow;
        private static Cpu.AnalogChannel _YLow;
        private static Cpu.AnalogChannel _XHigh;
        private static Cpu.AnalogChannel _YHigh;
        private static AnalogInput analog1 = null;
        private static AnalogInput analog2 = null;
        private static OutputPort digital1 = null;
        private static OutputPort digital2 = null;

        // Touchscreen(NDS)-> Netduino
        // Pin 1 (top)    -> D5,
        // Pin 2 (right)  -> D6,
        // Pin 3 (bottom) -> D7 and Analog In 0 (wired together) + 10k Pullup,
        // Pin 4 (left)   -> D8 and Analog In 1 (wired together) + 10k Pullup
        private TristatePort top;
        private TristatePort right;
        private TristatePort bottom;
        private TristatePort left;
        private AnalogInput senseX;
        private AnalogInput senseY;

        Point result = new Point();

        public enum PinMode
        {
            Tristate,
            Simple
        }
        public PinMode Mode = PinMode.Tristate;

        public const int ResolutionX = 700;
        public const int ResolutionY = 700;
        public bool FlipXY = false;
        public bool FlipX = false;
        public bool FlipY = false;
        double ResX = 0;
        double ResY = 0;

        const int Offset = 100;
        const int Delay = 100;
        const int MaxDistance = 5;
        long last;
        long now;

        public bool isMouse = true;
        public bool isTouchDown = false;

        public NDSTouchscreen(Cpu.Pin _top, Cpu.Pin _right, Cpu.Pin _bottom, Cpu.Pin _left,
            Cpu.AnalogChannel _senseX, Cpu.AnalogChannel _senseY,
            int ScreenWidth, int ScreenHeight)
        {
            Mode = PinMode.Tristate;

            top = new TristatePort(_top,true,false,Port.ResistorMode.Disabled);
            right = new TristatePort(_right, true, false, Port.ResistorMode.Disabled);
            bottom = new TristatePort(_bottom, true, false, Port.ResistorMode.Disabled);
            left = new TristatePort(_left, true, false, Port.ResistorMode.Disabled);

            senseX = new AnalogInput(_senseX);
            senseY = new AnalogInput(_senseY);

            ResX = ResolutionX / ScreenWidth;
            ResY = ResolutionY / ScreenHeight;
        }

        public NDSTouchscreen(Cpu.Pin xlow, Cpu.Pin ylow, Cpu.Pin xhigh, Cpu.Pin yhigh,
            Cpu.AnalogChannel _xlow, Cpu.AnalogChannel _ylow, Cpu.AnalogChannel _xhigh, Cpu.AnalogChannel _yhigh,
            int ScreenWidth, int ScreenHeight)
        {
            Mode = PinMode.Simple;

            XLow = xlow;
            YLow = ylow;
            XHigh = xhigh;
            YHigh = yhigh;

            _XLow = _xlow;
            _YLow = _ylow;
            _XHigh = _xhigh;
            _YHigh = _yhigh;

            ResX = ResolutionX / ScreenWidth;
            ResY = ResolutionY / ScreenHeight;
        }

        private int GetDistance(Point point1, Point point2)
        {
            if (point1 == null || point2 == null) return 0;

            int a = point2.x - point1.x;
            int b = point2.y - point1.y;

            return (int)System.Math.Sqrt((double)(a * a + b * b));
        }

        Point lastPoint = new Point(); 
        public Point Scan()
        {
            double _x = 0;
            double _y = 0;

            if (Mode == PinMode.Simple)
            {
                Thread.Sleep(20);

                analog1 = new AnalogInput(_XLow);
                analog2 = new AnalogInput(_XHigh);
                digital1 = new OutputPort(YLow, false);
                digital2 = new OutputPort(YHigh, true);

                _y = analog1.Read() * 1000;

                analog1.Dispose();
                analog2.Dispose();
                digital1.Dispose();
                digital2.Dispose();

                analog1 = new AnalogInput(_YLow);
                analog2 = new AnalogInput(_YHigh);
                digital1 = new OutputPort(XLow, false);
                digital2 = new OutputPort(XHigh, true);

                _x = analog1.Read() * 1000;

                analog1.Dispose();
                analog2.Dispose();
                digital1.Dispose();
                digital2.Dispose();

                analog1 = null;
                analog2 = null;
                digital1 = null;
                digital2 = null;
            }
            else if (Mode == PinMode.Tristate)
            {
                right.Active = true;    // Switch to output
                right.Write(false); 	// 0 V
                left.Active = true; 	// Switch to output
                left.Write(true);   	// Vcc

                _x = senseX.Read() * 1000;

                right.Active = false;   // Switch to input (floating)
                left.Active = false;    // Switch to input (floating)
                top.Active = true;      // Switch to output
                top.Write(true);        // Vcc
                bottom.Active = true;   // Switch to output
                bottom.Write(false);    // 0 V

                _y = senseY.Read() * 1000;

                top.Active = false; 	// Swith to input (floating)
                bottom.Active = false;  // Swith to input (floating)
            }

            if (_y < Offset || _x < Offset)
            {
                if (isTouchDown && TouchUpEvent != null) TouchUpEvent(new object(), new TouchEventArgs(lastPoint.x, lastPoint.y));

                isTouchDown = false;
                last = DateTime.Now.Ticks / 10000;

                return null;
            }
            else
            {
                result.x = (int)_x - Offset;
                result.y = (int)_y - Offset;

                if (FlipX) result.x = ResolutionX - result.x;
                if (FlipY) result.y = ResolutionY - result.y;
                
                if(FlipXY) 
                {
                    int tx = result.x;
                    result.x = result.y;
                    result.y = tx;
                }

                result.x = (int)(result.x / ResX);
                result.y = (int)(result.y / ResY);

                lastPoint.x = result.x;
                lastPoint.y = result.y;

                if (!isTouchDown)
                {
                    now = DateTime.Now.Ticks / 10000;
                    if (now - last >= Delay)
                    {
                        if (TouchDownEvent != null) TouchDownEvent(new object(), new TouchEventArgs(result.x, result.y));
                        last = now;
                        isTouchDown = true;

                        return result;
                    }

                    return null;
                }
                else
                {
                    if (GetDistance(lastPoint, result) > MaxDistance)
                    {
                        return null;
                    }

                    if (TouchMoveEvent != null) TouchMoveEvent(new object(), new TouchEventArgs(result.x, result.y));
                    return result;
                }
            }
        }
    }
}
